package com.lightsh.download;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.lightsh.network.Utility;

import android.os.Environment;
import android.os.Process;
import android.util.Log;

/**
 * 下载管理类，用来辅助进行文件下载，支持断点续传功能
 * @author Shu
 *
 */
public class DownloadHelper {
	private static final String TAG = "DownloadHelper";
	private static final boolean DEBUG = true;
	
	/**
	 * 初始化状态
	 */
	public static final int STATE_INIT = 0;
	/**
	 * 下载完成
	 */
	public static final int STATE_FINISH = 1;
	/**
	 * 下载出错
	 */
	public static final int STATE_ERROR = -1;
	/**
	 * 进度改变
	 */
	public static final int STATE_PROGRESS = 2;
	/**
	 * 存在相同任务,下载失败
	 */
	public static final int STATE_EXIST_SAME_TASK = -2;
	
	/**
	 * 取消下载
	 */
	public static final int STATE_USER_CANCEL = -3;
	
	/**
	 * 存储空间不足
	 */
	public static final int STATE_STORAGE_NOT_ENOUGH = -4;
	
	/**
	 * FIXME sd卡的apk保存目录
	 * 在sd卡存在的情况下，优先选择下载内容到sd卡。如果sd卡不存在，则下载文件到手机内存。
	 */
	public static final String FILE_PATH_SD = "/sdcard/Android/data/com.lightsh.network/Apk/";
	/**
	 * FIXME 手机内存中的apk保存目录
	 */
	public static final String FILE_PATH_INTERNAL = "/data/data/com.lightsh.network/Apk/";
	
	/**
	 * 同时下载的最大任务数
	 */
	private static final int DOWNLOAD_MAX_LIMIT = 2;
	
	/**
	 * 如果连接失败，最大的尝试连接次数
	 */
	private static final int DOWNLOAD_TRY_TIME = 3;
	
	/**
	 * 任务列表,并用作同步锁
	 */
	private List<DownloadInfo> mTaskList;
	
	/**
	 * 回调接口对象
	 */
	private DownloadStateChangeListener mListener;
	
	/**
	 * 线程池对象
	 */
	private ExecutorService mExecutorService;
	
	public DownloadHelper(DownloadStateChangeListener listener) {
		if(listener == null){
			throw new NullPointerException("listener cant be null!");
		}
		mListener = listener;
		mExecutorService = Executors.newFixedThreadPool(DOWNLOAD_MAX_LIMIT);
		mTaskList = new ArrayList<DownloadInfo>();
		
		checkDownloadPath();
	}
	private void checkDownloadPath(){
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
			File file = new File(FILE_PATH_SD);
			if(!file.exists()){
				file.mkdirs();
			}
		}
		File file = new File(FILE_PATH_INTERNAL);
		if(!file.exists()){
			file.mkdirs();
		}
		try{
			String command = "chmod 777 " + file.getAbsolutePath();
			Runtime runtime = Runtime.getRuntime();
			runtime.exec(command);
		}catch(IOException e){
			if(DEBUG){
				Log.d(TAG,"chmod fail!!!!");
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 保存下载任务的结构
	 */
	private static class DownloadInfo {
		public String mDownloadUrl;
		
		/**
		 * 文件名是下载的唯一标志，用来进行断点续传时使用。
		 */
		public String mFileName;
		/**
		 * 该任务的标志字符，供接口调用者识别任务
		 */
		public String mIdentify;
		
		/**
		 * 下载状态,初始化时为{@link DownloadHelper#STATE_INIT}
		 */
		public int mState;
		
		/**
		 * 当前的进度
		 */
		public int mProgress;
		
		/**
		 * 文件保存的完整路径
		 */
		public String mFullPath;
		
		DownloadInfo(String url, String fileName, String identify) {
			mDownloadUrl = url;
			mFileName = fileName;
			mIdentify = identify;
			mState = STATE_INIT;
			mProgress = 0;
			mFullPath = "";
		}
	}
	
	/**
	 * 进行下载,无论下载成功与否，都会通过回调返回信息。
	 * @param fileName
	 * @param downloadUrl
	 */
	public void download(String downloadUrl, String fileName, String identify) {
		DownloadInfo info = new DownloadInfo(downloadUrl, fileName, identify);
		// 先判断是否存在相同任务,如果存在则立即返回
		synchronized (mTaskList) {
			for(int i = 0; i < mTaskList.size(); ++i){
				if(mTaskList.get(i).mFileName.equalsIgnoreCase(info.mFileName)) {
					info.mState = STATE_EXIST_SAME_TASK;
					notifyStateChange(info);
					return;
				}
			}
			// 没有相同任务,进行下载
			mTaskList.add(info);
			ChildThread thread = new ChildThread(info);
			mExecutorService.execute(thread);
		}
		if(DEBUG){
			Log.e(TAG, "current download task list size:" + mTaskList.size());
		}
	}
	
	private class ChildThread extends Thread {
		private DownloadInfo mDownloadInfo;
		public ChildThread(DownloadInfo downloadInfo) {
			mDownloadInfo = downloadInfo;
		}
		
		
		public void run() {
			mDownloadInfo.mState = STATE_PROGRESS;
			// 进行下载操作
			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);			
			// apk文件是否存在
			String apkFilePath = checkIfApkExist(mDownloadInfo.mFileName);
			if(apkFilePath != null){
				if(DEBUG){
					Log.e(TAG, "apk exist,finish!");
				}
				mDownloadInfo.mFullPath = apkFilePath;
				mDownloadInfo.mState = STATE_FINISH;
				notifyStateChange(mDownloadInfo);
				return;
			}
			

			HttpURLConnection conn = null;
			long startPos = 0;
			File historyFile = null;
			FileOutputStream outputStream = null;
			InputStream inputStream = null;
			try{
				// 查询是否有记录存在,临时文件
				historyFile = checkHistoryFile(mDownloadInfo.mFileName + ".temp");
				if (historyFile.exists()) {
					startPos = historyFile.length();
				} else {
					checkDownloadPath();
					if(!historyFile.createNewFile()){
						throw new Exception();
					}
					try {
						String command = "chmod 777 " + historyFile.getAbsolutePath();
						Runtime runtime = Runtime.getRuntime();
						runtime.exec(command);
					} catch (IOException e) {
						Log.i(TAG, "chmod fail!!!!");
						e.printStackTrace();
					}
				}
				outputStream = new FileOutputStream(historyFile, true);
			}catch(Exception e){
				if(DEBUG){
					Log.e(TAG, "create download file fail!");
					e.printStackTrace();
				}
				mDownloadInfo.mState = STATE_ERROR;
				notifyStateChange(mDownloadInfo);
				return;
			}
			mDownloadInfo.mFullPath = historyFile.getParent() + "/" + mDownloadInfo.mFileName;
			
			try{
				int tryCount = 0;
				for (; tryCount < DOWNLOAD_TRY_TIME; tryCount++) {
					try{
						if (mDownloadInfo.mState == STATE_USER_CANCEL) {
							notifyStateChange(mDownloadInfo);
							return;
						}
						
						// 已经下载好的数据长度
						long historyCount = startPos;

						conn = Utility.getConnection(mDownloadInfo.mDownloadUrl);
						conn.setDoInput(true);
						conn.setRequestProperty("accept", "*/*");
						
						// 设置Range值
						conn.addRequestProperty("Range", "bytes=" + startPos + "-");

						conn.connect();
						int resCode = conn.getResponseCode();
						if (resCode != HttpURLConnection.HTTP_OK && resCode != HttpURLConnection.HTTP_PARTIAL) {
							// 连接失败
							if(resCode == 416){
								// 请求的文件长度出现问题
								startPos = 0;
								outputStream = new FileOutputStream(historyFile, false);
							}
							continue;
						}
						inputStream = conn.getInputStream();						
						long currentFileSize = conn.getContentLength();
						
						// 检查是否足够空间
						long availableSize = 0;
						if(historyFile.getAbsolutePath().startsWith(FILE_PATH_SD)){
							availableSize = Utility.getSdCardAvailableSize();
						}else{
							availableSize = Utility.getPhoneAvailableSize();
						}
						if(currentFileSize * 2 > availableSize){
							if(DEBUG){
								Log.e(TAG, "storage not enough!download cancel!");
							}
							// 内存不够
							mDownloadInfo.mState = STATE_STORAGE_NOT_ENOUGH;
							notifyStateChange(mDownloadInfo);
							return;
						}

						byte[] buffer = new byte[4096];
						long readCount = 0;
						int readNum = 0;
						int prevPercent = -1;

						long timeTickLast = System.currentTimeMillis(); // < 记录刷新进度时间

						while (readNum != -1 && readCount < currentFileSize) {

							// Thread.sleep(3000);

							if (mDownloadInfo.mState == STATE_USER_CANCEL) {
								// 取消下载: 跳出写入循环
								break;
							}
							readNum = inputStream.read(buffer);
							if (readNum > 0) {
								outputStream.write(buffer, 0, readNum);
								outputStream.flush();
								readCount += readNum;
								startPos += readNum;
								int percent = (int) (startPos * 100 / (historyCount + currentFileSize));
								if (percent > prevPercent) {
									prevPercent = percent;
									// Notify

									mDownloadInfo.mProgress = percent;
									// 判断刷新时间间隔是否太短
									long timeTickNow = System.currentTimeMillis();
									if (timeTickNow - timeTickLast > 300 || percent == 0 || percent == 100) {
										timeTickLast = timeTickNow;
										notifyStateChange(mDownloadInfo);
									}
								}
							}
						}
						if (mDownloadInfo.mState == STATE_USER_CANCEL) {
							// 取消下载,是否删除临时文件？
							if (historyFile.exists()) {
								//historyFile.delete();
							}
							notifyStateChange(mDownloadInfo);
							return;
						} else {
							if (DEBUG) {
								Log.d(TAG, "download finish");
							}

							if (readCount < currentFileSize) {
								// 出错
								throw new Exception("DownloadService error: apk file size not right!");
							}
							
							// 成功,需要移动文件
							if(!Utility.renameFile(historyFile, new File(mDownloadInfo.mFullPath))){
								throw new Exception("move apk temp file fail!");
							}
							mDownloadInfo.mState = STATE_FINISH;
							notifyStateChange(mDownloadInfo);
							return;
						}
					} catch(Exception e){
						if(DEBUG){
							Log.e(TAG, "download file exception:");
							e.printStackTrace();
						}
						// 连接断开，继续重连
						continue;
					} finally {
						if(inputStream != null){
							try {
								inputStream.close();
							} catch (IOException e) {
								if(DEBUG){
									e.printStackTrace();
								}								
							}
							inputStream = null;
						}
						if(conn != null){
							conn.disconnect();
							conn = null;
						}
					}

				}
				// 这里应该是必须回调的
				if(tryCount == DOWNLOAD_TRY_TIME){
					// 下载失败
					mDownloadInfo.mState = STATE_ERROR;
					notifyStateChange(mDownloadInfo);
				}else{
    				throw new RuntimeException("DownloadHelper end with no notify!");
    			}
			}finally {
				// 最后关闭输入流
				if(outputStream != null){
					try{
						outputStream.close();
					}catch(Exception e){
						if(DEBUG){
							e.printStackTrace();
						}
					}
					outputStream = null;
				}
			}
			
		}
	}
	/**
	 * 查询历史记录的文件
	 * @param fileName 对应的文件名
	 * @return 返回对应的文件,如果存在历史，该文件存在并且有内容,如果不存在,则是不存在的文件
	 */
	private static final File checkHistoryFile(String fileName) {
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
			// 有sdcard
			File sdFile = new File(FILE_PATH_SD + fileName);
			if(sdFile.exists()){
				return sdFile;
			}
			File internalFile = new File(FILE_PATH_INTERNAL + fileName);
			if(internalFile.exists()){
				// 考录是否将数据移动到sdcard
				return internalFile;
			} else {
				return sdFile;
			}
			
		} else {
			File internalFile = new File(FILE_PATH_INTERNAL + fileName);
			return internalFile;
		}
	}
	/**
	 * 是否存在已经下载好的apk文件,如果存在，返回完整路径，否则返回null
	 */
	private static final String checkIfApkExist(String fileName){
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
			// 有sdcard
			File sdFile = new File(FILE_PATH_SD + fileName);
			if(sdFile.exists()){
				return sdFile.getAbsolutePath();
			}			
		} else {
			File internalFile = new File(FILE_PATH_INTERNAL + fileName);
			if(internalFile.exists()){
				return internalFile.getAbsolutePath();
			}
		}
		return null;
	}
	
	/**
	 * 取消下载
	 */
	public void cancelDownload(String fileName){
		synchronized (mTaskList) {
			for(int i = 0; i < mTaskList.size(); ++i){
				if(mTaskList.get(i).mFileName.equalsIgnoreCase(fileName)){
					mTaskList.get(i).mState = STATE_USER_CANCEL;
					return;
				}
			}
		}
	}
	
	/**
	 * 进行下载完成的回调
	 * @param downloadInfo 下载任务信息
	 */
	private void notifyStateChange(DownloadInfo downloadInfo) {
		synchronized (mTaskList) {
			mListener.onDownloadStateChange(downloadInfo.mIdentify, downloadInfo.mFileName, downloadInfo.mDownloadUrl, downloadInfo.mState, downloadInfo.mProgress, downloadInfo.mFullPath);
			
			// 判断是否需要从列表中删除
			if(downloadInfo.mState == STATE_ERROR 
					|| downloadInfo.mState == STATE_EXIST_SAME_TASK 
					|| downloadInfo.mState == STATE_FINISH 
					|| downloadInfo.mState == STATE_USER_CANCEL
					|| downloadInfo.mState == STATE_STORAGE_NOT_ENOUGH){
				for(int i = 0; i < mTaskList.size() ; ++i){
					if(mTaskList.get(i).mFileName.equalsIgnoreCase(downloadInfo.mFileName)){
						mTaskList.remove(i);
						break;
					}
				}
			}
		}
	}
	
	/**
	 * 下载完成/下载失败/进度改变 后的回调接口
	 * 注意不要在该回调中做复杂操作
	 *
	 */
	public static interface DownloadStateChangeListener {
		/**
		 * 注意：在该回调中，尽量不能调用使用锁的代码段，否则会造成死锁情况!
		 */
		public void onDownloadStateChange(String identify, String fileName, String downloadUrl, int state, int progress, String fullPath);
	}
	
}
